import * as THREE from "three";
import type { Game } from "./Game";

type MouseFunctionType = (event: MouseEvent, mouse: THREE.Vector2) => void;
type EventType = "mousedown" | "mouseup" | "mousemove";

/** 
 * Manages mouse and keyboard input
 */
export class InputManager {

    mouse = new THREE.Vector2();

    private _isLeftMouseDown = false;

    private _isMiddleMouseDown = false;

    private _isRightMouseDown = false;

    private mousedownQueue: MouseFunctionType[];
    private mouseupQueue: MouseFunctionType[];
    private mousemoveQueue: MouseFunctionType[];

    get isLeftMouseDown(): boolean {
        return this._isLeftMouseDown;
    }

    get isMiddleMouseDown(): boolean {
        return this._isMiddleMouseDown;
    }

    get isRightMouseDown(): boolean {
        return this._isRightMouseDown;
    }

    constructor(game: Game) {

        this.mousedownQueue = [];
        this.mousemoveQueue = [];
        this.mouseupQueue = [];

        const targetElement = game.targetElement;
        targetElement.addEventListener('mousedown', this.onmousedown, false);
        targetElement.addEventListener('mouseup', this.onmouseup, false);
        targetElement.addEventListener('mousemove', this.onmousemove, false);
        targetElement.addEventListener('contextmenu', (event) => event.preventDefault(), false);
    }

    add(type: EventType, fn: MouseFunctionType): boolean {

        const queue = this.getQueue(type);

        if (queue === undefined) return false;

        if (queue.indexOf(fn) === -1) {

            queue.push(fn);

            return true;

        }

    }

    remove(type: EventType, fn: MouseFunctionType): boolean {

        const queue = this.getQueue(type);

        if (queue === undefined) return false;

        const index = queue.indexOf(fn);

        if (index !== -1) {

            queue.splice(index, 1);

            return true;

        }

    }

    private getQueue(type: EventType): MouseFunctionType[] | undefined {

        switch (type) {
            case "mousedown":
                return this.mousedownQueue;
            case "mousemove":
                return this.mousemoveQueue;
            case "mouseup":
                return this.mouseupQueue;
            default:
                return;
        }

    }

    private onmousedown = (event: MouseEvent) => {
        if (event.button === 0) {
            this._isLeftMouseDown = true;
        }
        if (event.button === 1) {
            this._isMiddleMouseDown = true;
        }
        if (event.button === 2) {
            this._isRightMouseDown = true;
        }

        this.mousedownQueue.forEach(fn => {
            fn(event, this.mouse);
        });

    };

    private onmouseup = (event: MouseEvent) => {
        if (event.button === 0) {
            this._isLeftMouseDown = false;
        }
        if (event.button === 1) {
            this._isMiddleMouseDown = false;
        }
        if (event.button === 2) {
            this._isRightMouseDown = false;
        }

        this.mouseupQueue.forEach(fn => {
            fn(event, this.mouse);
        });
    };

    private onmousemove = (event: MouseEvent) => {
        const buttons = event.buttons;

        this._isLeftMouseDown = !!(buttons & 1);
        this._isRightMouseDown = !!(buttons & 2);
        this._isMiddleMouseDown = !!(buttons & 4);

        this.mouse.x = event.clientX;
        this.mouse.y = event.clientY;

        this.mousemoveQueue.forEach(fn => {
            fn(event, this.mouse);
        });
    };

}